/*
*  Copyright (c) 2006-2020, Chukie
*
*  SPDX-License-Identifier: Apache-2.0
*
*  @file     : cmux_channel.c
*
*  @brief    :
*  Website   : https://gitee.com/open-embedded
*
*
*  Change Logs:
*  Date           Author          Notes
*  2020-06-28     zhaocj       The first version
*/
/* Includes -----------------------------------------------------------------*/
#include "cmux.h"
#define DBG_TAG "cmux-channel"

#ifdef CELLULAR_CMUX_USING_DEBUG
    #define DBG_LVL DBG_LOG
#else
    #define DBG_LVL DBG_INFO
#endif
#include <rtdbg.h>
/* Private typedef ----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
/* Extern variables ---------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
extern int cmux_raw_send(cmux_t *cmux, uint8_t dlci, uint8_t frame_type, uint8_t *data, size_t len);

static rt_err_t cmux_channel_open(rt_device_t dev, rt_uint16_t oflag)
{
    rt_uint32_t event;
    RT_ASSERT(dev != RT_NULL && NULL != dev->user_data);
    cmux_channel_t *channel = (cmux_channel_t *)(dev->user_data);
    RT_ASSERT(channel->parent);

    rt_base_t level = rt_hw_interrupt_disable();
    rt_ringbuffer_reset(&channel->rb);
    rt_hw_interrupt_enable(level);
    cmux_raw_send(channel->parent, channel->dlci, CMUX_FRAME_TYPE_SABM | CMUX_CONTROL_PF, RT_NULL, 0);

    if (RT_EOK != rt_event_recv(&channel->parent->event, CMUX_EVENT_CHANNEL_CREATE_ACK(channel->dlci), \
                                RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(3000), &event))
    {
        CMUX_WARN("Open channel %d failed!", channel->dlci);
    }
    return RT_EOK;
}

/**
 * close virtual channel
 *
 * @param dev       the point of virtual device
 *
 * @return  the result
 */
static rt_err_t cmux_channel_close(rt_device_t dev)
{
    rt_uint32_t event;
    RT_ASSERT(dev != RT_NULL && NULL != dev->user_data);
    cmux_channel_t *channel = (cmux_channel_t *)(dev->user_data);
    RT_ASSERT(channel->parent);
    cmux_raw_send(channel->parent, channel->dlci, CMUX_FRAME_TYPE_DISC | CMUX_CONTROL_PF, RT_NULL, 0);

    rt_event_recv(&channel->parent->event, CMUX_EVENT_CHANNEL_CREATE_ACK(channel->dlci), \
                  RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(1000), &event);
    return RT_EOK;
}

/**
 * write data into virtual channel
 *
 * @param dev       the point of virtual device
 * @param pos       offset
 * @param buffer    the data you want to send
 * @param size      the length of buffer
 *
 * @return  the result
 */
static rt_size_t cmux_channel_write(struct rt_device *dev,
                                    rt_off_t pos,
                                    const void *buffer,
                                    rt_size_t size)
{
    RT_ASSERT(dev != RT_NULL && NULL != dev->user_data);
    cmux_channel_t *channel = (cmux_channel_t *)(dev->user_data);

    return cmux_raw_send(channel->parent, channel->dlci, CMUX_FRAME_TYPE_UIH, (uint8_t *)buffer, size);
}

/**
 * write data into virtual channel
 *
 * @param dev       the point of virtual device
 * @param pos       offset
 * @param buffer    the buffer you want to store
 * @param size      the length of buffer
 *
 * @return  the result
 */
static rt_size_t cmux_channel_read(struct rt_device *dev,
                                   rt_off_t pos,
                                   void *buffer,
                                   rt_size_t size)
{
    rt_size_t length = 0;
    RT_ASSERT(dev != RT_NULL && NULL != dev->user_data);

    cmux_channel_t *channel = (cmux_channel_t *)(dev->user_data);

    rt_base_t level = rt_hw_interrupt_disable();
    length = rt_ringbuffer_get(&channel->rb, buffer, size);
    rt_hw_interrupt_enable(level);
    return length;
}

/* virtual serial ops */
#ifdef RT_USING_DEVICE_OPS
const struct rt_device_ops cmux_device_ops =
{
    RT_NULL,
    cmux_channel_open,
    cmux_channel_close,
    cmux_channel_read,
    cmux_channel_write,
    RT_NULL,
};
#endif


/* Public function prototypes -----------------------------------------------*/
int cmux_add_channel(cmux_t *cmux, const char *name, rt_uint16_t flags, size_t buf_size)
{
    cmux_channel_t *channel = NULL;
    static uint8_t used_channel = 0;
    RT_ASSERT(cmux);
    if (rt_device_find(name))
    {
        CMUX_INFO("%s has been added!", name);
        return RT_EOK;
    }
    if (used_channel >= CELLULAR_CMUX_CHANNEL_NUM)
    {
        CMUX_ERR("No avalibale channel!");
        return -RT_ENOSYS;
    }
    if (!buf_size)
    {
        CMUX_ERR("buf size invalid!");
        return -RT_ENOSYS;
    }

    channel = &cmux->channel_list[used_channel];
    if (channel->buffer)
    {
        rt_free(channel->buffer);
    }
    channel->buffer = rt_malloc(buf_size);
    if (!channel->buffer)
    {
        CMUX_ERR("No mem to create channel buffer!");
        return -RT_ENOMEM;
    }
    channel->parent = cmux;
    channel->dlci = used_channel + 1;
    used_channel++;
    struct rt_device *device = &channel->device;
    device->type = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    device->ops = &cmux_device_ops;
#else
    device->init = RT_NULL;
    device->open = cmux_channel_open;
    device->close = cmux_channel_close;
    device->read = cmux_channel_read;
    device->write = cmux_channel_write;
    device->control = RT_NULL;
#endif

    device->user_data = (void *)channel;
    rt_ringbuffer_init(&channel->rb, channel->buffer, buf_size);

    if (flags & RT_DEVICE_FLAG_DMA_RX)
        rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_DMA_RX);
    else
        rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
    CMUX_INFO("Add cmux channel->DLCI[%d] %s<-->%s", channel->dlci, cmux->device->parent.name, name);
    return RT_EOK;
}
/* End of file****************************************************************/

